#ifndef _RETRO_SCRIPTINTERFACE
#define _RETRO_SCRIPTINTERFACE

namespace Retro
{
  namespace Scripting
  {
    enum ValueType
    {
      TypeUnknown = -1,
      TypeNull,
      TypeInteger,
      TypeLong,
      TypeUnsigned,
      TypeFloat,
      TypeDouble,
      TypeBoolean,
      TypeString,
      TypeFunction,
      TypeScript,
      TypeObject,
      TypeNumber = TypeDouble,
      TypeColor = TypeString,
    };

    enum ErrorType
    {
      ErrorUnknown = -1,
      ErrorNone,
      ErrorSyntax,
      ErrorMemory,
      ErrorRuntime,
    };

    struct ScriptValue
    {
      PVOID     value;
      ValueType type;
      PCSTR     name;
      UINT      param;
    };

    class ScriptInterface;

    typedef VOID (*ScriptCallback)(ScriptInterface* iInterface);

    INL PSTR StringToAnsi(PCWSTR iString)
    {
      PSTR wString;

      SIZE_T wLength  = wcslen(iString);
      CHAR wDefChar   = '_';

      wString = new CHAR[wLength + 1];

#ifdef UNICODE
      WideCharToMultiByte(
        CP_ACP, WC_NO_BEST_FIT_CHARS,
        iString, -1,
        wString,
        (INT)wLength + 1,
        &wDefChar,
        NULL);

#else
      strcpy(wString, iString);
#endif

      return wString;
    }

    INL PWSTR StringToUnicode(PCSTR iString)
    {
      if (!iString)
        return NULL;

      PWSTR wString;
      SIZE_T wLength = strlen(iString);

      wString = new WCHAR[wLength + 1];

#ifdef UNICODE
      MultiByteToWideChar(
        CP_ACP,
        0,
        iString,
        (INT)wLength + 1,
        wString,
        (INT)wLength + 1);
#else
      wcscpy(wString, iString);
#endif

      return wString;
    }

    INL VOID ReleaseString(PCSTR iString)
    {
#ifdef UNICODE
      delete[] iString;
#endif
    }

    class DLLEXPORT NOVTABLE ScriptInterface
    {
    private:
      Generic::Vector<PTSTR, 10> mStrings;
      Data::ScriptLanguage mLang;
      ErrorType mError;

    protected:
      INL VOID  setError(ErrorType iType)
      {
        mError = iType;
      }

      INL VOID addBufferString(PTSTR iString)
      {
        if (iString)
          mStrings.add(iString);
      }

    public:
      virtual VOID add(ScriptValue* iValue) = 0;
      virtual VOID add(ScriptValue* iValues, UINT iCount, PCSTR iName) = 0;

      INL VOID add(INT iValue, PCSTR iName)
      {
        ScriptValue wValue = { &iValue, TypeInteger, iName };
        add(&wValue);
      }
      INL VOID add(LONG iValue, PCSTR iName)
      {
        ScriptValue wValue = { &iValue, TypeLong, iName };
        add(&wValue);
      }
      INL VOID add(UINT iValue, PCSTR iName)
      {
        ScriptValue wValue = { &iValue, TypeUnsigned, iName };
        add(&wValue);
      }
      INL VOID add(ULONG iValue, PCSTR iName)
      {
        ScriptValue wValue = { &iValue, TypeUnsigned, iName };
        add(&wValue);
      }
      INL VOID add(ULONGLONG iValue, PCSTR iName)
      {
        ScriptValue wValue = { &iValue, TypeUnsigned, iName };
        add(&wValue);
      }
      INL VOID add(FLOAT iValue, PCSTR iName)
      {
        ScriptValue wValue = { &iValue, TypeFloat, iName };
        add(&wValue);
      }
      INL VOID add(DOUBLE iValue, PCSTR iName)
      {
        ScriptValue wValue = { &iValue, TypeDouble, iName };
        add(&wValue);
      }
      INL VOID add(bool iValue, PCSTR iName)
      {
        ScriptValue wValue = { &iValue, TypeBoolean, iName };
        add(&wValue);
      }
      INL VOID add(PCSTR iValue, PCSTR iName)
      {
        ScriptValue wValue = { (PVOID)iValue, TypeString, iName };
        add(&wValue);
      }
      INL VOID add(ScriptCallback iValue, PCSTR iName)
      {
        ScriptValue wValue = { iValue, TypeFunction, iName };
        add(&wValue);
      }
      INL VOID addBool(BOOL iValue, PCSTR iName)
      {
        ScriptValue wValue = { &iValue, TypeBoolean, iName };
        add(&wValue);
      }
      INL VOID addNull(BOOL iValue, PCSTR iName)
      {
        ScriptValue wValue = { &iValue, TypeNull, iName };
        add(&wValue);
      }

      virtual VOID  push(PVOID iValue, ValueType iType) = 0;
      virtual VOID  push(ScriptValue* iValues, UINT iCount) = 0;

      INL VOID push(INT iValue)       { push(&iValue,       TypeInteger ); }
      INL VOID push(LONG iValue)      { push(&iValue,       TypeLong    ); }
      INL VOID push(UINT iValue)      { push(&iValue,       TypeUnsigned); }
      INL VOID push(FLOAT iValue)     { push(&iValue,       TypeFloat   ); }
      INL VOID push(DOUBLE iValue)    { push(&iValue,       TypeDouble  ); }
      INL VOID push(bool iValue)      { push(&iValue,       TypeBoolean ); }
      INL VOID push(PCSTR iValue)     { push((PVOID)iValue, TypeString  ); }
      INL VOID push(PCWSTR iValue)
      {
        PSTR wString = StringToAnsi(iValue);
        push(wString);
        ReleaseString(wString);
      }
      INL VOID pushBool(BOOL iValue)  { push(&iValue,       TypeBoolean ); }
      INL VOID pushNull()             { push(NULL,          TypeNull    ); }

      virtual VOID  get(UINT iIndex, PVOID oValue, ValueType iType) = 0;
      virtual INT   getParamCount() = 0;

      virtual ValueType getType(UINT iIndex) = 0;

      INL INT     getInt(UINT iIndex)
      {
        INT wValue;
        get(iIndex, &wValue, TypeInteger);
        return wValue;
      }
      INL LONG    getLong(UINT iIndex)
      {
        LONG wValue;
        get(iIndex, &wValue, TypeLong);
        return wValue;
      }
      INL UINT    getUnsigned(UINT iIndex)
      {
        UINT wValue;
        get(iIndex, &wValue, TypeUnsigned);
        return wValue;
      }
      INL FLOAT   getFloat(UINT iIndex)
      {
        FLOAT wValue;
        get(iIndex, &wValue, TypeFloat);
        return wValue;
      }
      INL DOUBLE  getDouble(UINT iIndex)
      {
        DOUBLE wValue;
        get(iIndex, &wValue, TypeDouble);
        return wValue;
      }
      INL BOOL    getBool(UINT iIndex)
      {
        BOOL wValue;
        get(iIndex, &wValue, TypeBoolean);
        return wValue;
      }
      INL PCSTR   getAnsi(UINT iIndex)
      {
        PCSTR wValue;
        get(iIndex, &wValue, TypeString);
        return wValue;
      }
      INL PCWSTR  getUnicode(UINT iIndex)
      {
        PCSTR wValue;
        get(iIndex, &wValue, TypeString);

        addBufferString(StringToUnicode(wValue));
        return mStrings.getTop();
      }
      INL PCTSTR  getString(UINT iIndex)
      {
#ifdef UNICODE
        return getUnicode(iIndex);
#else
        return getAnsi(iIndex);
#endif
      }
      INL COLOR   getColor(UINT iIndex)
      {
        PCSTR wValue;
        get(iIndex, &wValue, TypeString);
        return strtoul(wValue, NULL, 16);
      }

      INL Geom::Point   getPoint(UINT iIndex)
      {
        return Geom::Point::get(
          getInt(iIndex),
          getInt(iIndex + 1));
      }
      INL Geom::Bounds  getBounds(UINT iIndex)
      {
        return Geom::Bounds::get(
          getInt(iIndex),
          getInt(iIndex + 1),
          getInt(iIndex + 2),
          getInt(iIndex + 3));
      }

      INL ErrorType getError()
      {
        return mError;
      }

      virtual PCTSTR getErrorMessage() = 0;

      INL const Generic::Vector<PTSTR, 10>& getStringBuffer()
      {
        return mStrings;
      }

      INL VOID clearStringBuffer()
      {
        for (UINT i = 0; i < mStrings.getLength(); ++i)
          delete[] mStrings[i];
        mStrings.clear();
      }

      virtual BOOL  run(PCSTR iScript) = 0;
      virtual BOOL  call(PCSTR iName) = 0;
      virtual BOOL  validate(PCSTR iScript) = 0;

      INL Data::ScriptLanguage  getLanguage()
      {
        return mLang;
      }

      INL ScriptInterface(Data::ScriptLanguage iLang)
      {
        mLang = iLang;
        mError = ErrorNone;
        mStrings.setSize(100);
      }

      INL virtual ~ScriptInterface()
      {
        clearStringBuffer();
      }
    };
  } // namespace Scripting
} // namespace Retro
#endif
